package cn.zhaozhiqi.service.impl;

import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.zhaozhiqi.constants.Constants;
import cn.zhaozhiqi.constants.ErrorCode;
import cn.zhaozhiqi.mapper.LoginMapper;
import cn.zhaozhiqi.mapper.PermissionMapper;
import cn.zhaozhiqi.pojo.app.dto.AdminLoginDto;
import cn.zhaozhiqi.pojo.app.dto.AppLoginDto;
import cn.zhaozhiqi.pojo.app.vo.LoginResultVo;
import cn.zhaozhiqi.pojo.uaa.vo.LoginVo;
import cn.zhaozhiqi.pojo.domain.Login;
import cn.zhaozhiqi.pojo.manager.dto.UpdatePasswordDto;
import cn.zhaozhiqi.pojo.vo.WXRegisterVo;
import cn.zhaozhiqi.remote.dto.LoginDto;
import cn.zhaozhiqi.result.JSONResult;
import cn.zhaozhiqi.service.ILoginService;
import cn.zhaozhiqi.template.WeChatTemplate;
import cn.zhaozhiqi.utils.AssertUtil;
import cn.zhaozhiqi.utils.NameUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.util.Date;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;




/**
 * <p>
 * 登录表 服务实现类
 * </p>
 *
 * @author zhaozhiqi
 * @since 2024-12-21
 */
@Service
public class LoginServiceImpl extends ServiceImpl<LoginMapper, Login> implements ILoginService {


    @Autowired
    private WeChatTemplate weChatTemplate;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private LoginMapper loginMapper;

    /**
     *退出登录，清除redis缓存操作
     * @param id 前端传入参数
     * @return
     */
    @Override
    public Object exitLogin(Long id) {
        Login login = super.getById(id);
        String key = Constants.Redis.PERMISSION + login.getId();
        AssertUtil.isTrue(redisTemplate.delete(key),ErrorCode.REDIS_LOGIN_DELETE_ERROR);
        return true;
    }

    /**
     * 根据admin名称拿到超管账号,若有就返回true
     * @param admin
     */
    @Override
    public Boolean getAdmin(String admin) {
        if(loginMapper.getAdmin(admin) != null){
            return true;
        }
        return false;
    }
//添加一个用户名为admin的超管账号
    @Override
    public void setAdmin() {
        Login login = new Login();
        //根据hutool内的id工具类的雪花算法生成id
        Snowflake snowflakeID = IdUtil.createSnowflake(2, 4);
        long AdminId = snowflakeID.nextId();
        login.setId(AdminId);
        login.setUsername("admin");
        login.setPassword(bCryptPasswordEncoder.encode("123456"));
        login.setType(0);
        login.setEnabled(true);
        login.setAvatar("https://img1.baidu.com/it/u=1582939905,3315609136&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1700672400&t=6f248d173aa0b0dc8d9eb3c9106e89de");
        login.setAdmin(true);
        login.setNickName("闪闪的管理员");
        login.setName("管理员");
        login.setCreateTime(new Date());
        super.save(login);
    }

    /**
     * app统一登录的方法
     * @param dto
     * @return
     */
    @Override
    public Object applogin(AppLoginDto dto) {
        //拿到openid
        WXRegisterVo wxRegisterVo = weChatTemplate.getOpenid(dto.getCode());
        //拿到openid我们需要先判断是否数据库中有这个数据，这就需要重新写一个专属mapper。这样我们可以用mybatisplus中的工具类生成
        LambdaQueryWrapper<Login> wapper = new LambdaQueryWrapper<Login>()
                .eq(Login::getOpenId, wxRegisterVo.getOpenid())
                .eq(Login::getType, dto.getType());
        //通过ServiceImpl内置的方法为我们寻找是否有满足要求的Driver
        Login login = super.getOne(wapper);
        //判断拿到的login是否为空，如果不为空的话就直接抛出异常,使用hutool工具包
        AssertUtil.isNotNull(login, ErrorCode.PARAM_ACCOUNT_EXIST);

        LoginResultVo loginResultVo = getLoginResultVo(login);


        return loginResultVo;
    }


    /**
     * 管理系统登录校验方法
     * @param dto
     * @return
     */
    @Override
    public Object adminLogin(AdminLoginDto dto) {
        //判断用户名和密码是否一致
        Login admin = checkUsernameAndPassword(dto);
        //根据返回的对象封装返回前端的数据
        LoginResultVo loginResultVo = getLoginResultVo(admin);
        return loginResultVo;
    }


    /**
     * 修改超管的账号密码
     * @param dto
     * @return
     */
    @Override
    public Object adminUpdatePassWord(UpdatePasswordDto dto) {
        //根据id拿到login
        Login login = super.getById(dto.getId());
        /*若密码相同，就进行修改密码的操作*/
        //判断密码是否相同
        boolean matches = bCryptPasswordEncoder.matches(dto.getOldPassword(), login.getPassword());
        AssertUtil.isTrue(matches,ErrorCode.ACCOUNT_LOGIN_ERROR);
        //将新的密码进行加密
        String newPassword = bCryptPasswordEncoder.encode(dto.getNewPassword());
        login.setPassword(newPassword);
        super.updateById(login);
        return true;
    }



    /**
     * 校验前端传入的账号和密码是否与数据库一致,若一致，返回login对象
     * @param dto 前端传入的，包含账号和密码的一个对象
     * @return 若数据库中有，就返回此login对象
     */
    private Login checkUsernameAndPassword(AdminLoginDto dto) {
        LambdaQueryWrapper<Login> wapper = new LambdaQueryWrapper<Login>()
                .eq(Login::getUsername, dto.getUsername());
        Login login = super.getOne(wapper);
        AssertUtil.isNotNull(login,ErrorCode.PARAM_ACCOUNT_EXIST);
        //
        String encode = bCryptPasswordEncoder.encode(dto.getPassword());
        System.out.println(encode);
        boolean matches = bCryptPasswordEncoder.matches(dto.getPassword(), login.getPassword());
        AssertUtil.isTrue(matches,ErrorCode.ACCOUNT_LOGIN_ERROR);
        return login;
    }

    /**
     * 根据login信息得到jwt、登录后详细属性、权限
     * @param login
     * @return 返回前端的相关信息，包括jwt、登录后详细属性、权限
     */
    private LoginResultVo getLoginResultVo(Login login) {
        //通过satoken，将id和openid信息整合到JWT串中；一般情况下权限不需要整合到里面；
        StpUtil.login(login.getId(), SaLoginConfig
                .setExtra("openId", login.getOpenId()));
        //返回前端的对象
        LoginResultVo loginResultVo = new LoginResultVo();
        //加入satoken串
        loginResultVo.setSatoken(StpUtil.getTokenValue());
        //加入登录后的基本信息
        LoginVo loginVo = new LoginVo();
        BeanUtils.copyProperties(login,loginVo);
        loginResultVo.setLoginVo(loginVo);
        //加入权限信息
        List<String> list = permissionMapper.getPermissionsById(login.getId());
        loginResultVo.setPermissions(list);
        /*
        查到权限的同时加到redis中.我们一般使用全局唯一的id作为key。但是使用id存的不同数据需要区分
        因此在id前面拼接一个存入那些数据的名称，存权限就加permission字段即可
         */
        String key = Constants.Redis.PERMISSION + login.getId();
        redisTemplate.opsForValue().set(key,list);
        //把login表中的基础信息都上传到redis中
        String loginVoKey = Constants.Redis.LOGINVO + login.getId();
        redisTemplate.opsForValue().set(loginVoKey,loginVo);
        return loginResultVo;
    }


    /**
     * 其他微服务注册账号后调用方法,
     * @param dto 其他微服务注册账号后传入的信息
     * @return 返回保存成功或失败信息
     */
    @Override
    public JSONResult register(LoginDto dto) {
        Login login = new Login();
        //使用java自带方法，将dto对象内属性映射到login对象内属性。属性名和数据类型必须一致才可以
        BeanUtils.copyProperties(dto,login);
        login.setAvatar("https://img2.baidu.com/it/u=1254508239,1582098245&fm=253&fmt=auto&app=138&f=JPEG?w=475&h=631");
        login.setNickName(NameUtil.getName());
        login.setCreateTime(new Date());
        boolean save = super.save(login);
        return save?JSONResult.success():JSONResult.error();
    }


}
